﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.VisualStyles;
using Noris.Tools.FrxEditor.Components;
using Noris.Schedule.Support;
using Noris.Tools.FrxEditor.Editor;

namespace Noris.Tools.FrxEditor.ComponentsLayer
{
    /// <summary>
    /// Datový objekt ScrollBar, který dokáže provést Renderování standardním Windows rendererem, má Value typu float
    /// a je přiměřeně interaktivní, ale není to Control (z důvodu nehezkého přemisťování controlu při resize parenta)
    /// </summary>
    public class ScrollBar : IInteractiveObject
    {
		#region Konstrukce
		/// <summary>
		/// Konstruktor
		/// </summary>
        public ScrollBar()
        {
            this._Minimum = 0f;
            this._Maximum = 100f;
            this._Showing = 25f;
            this._CurrentBegin = 0f;
            this._Bounds = new RectangleF(10, 10, 100, 15);
            this._Orientation = System.Windows.Forms.Orientation.Horizontal;
            this.LayerStandard = 0;
            this.LayerInteractive = 0;
            this.CurrentActivePart = ScrollBarPart.None;
            this.CurrentMouseState = EditMouseState.None;
        }
        private float _Minimum;
        private float _Maximum;
        private float _Showing;
        private float _CurrentBegin;
        private RectangleF _Bounds;
        private Orientation _Orientation;
        #endregion
        #region Public property - Data a vzhled
        /// <summary>
        /// Data ScrollBaru zabalená do jednoho objektu.
        /// Data lze i setovat, pak se provedou všechny přepočty jen jedenkrát, a navíc lze volit zda má dojít k vyvolání eventu ValueChanged.
        /// </summary>
        public ScrollBarData ScrollBarData
        {
            get
            {
                return new ScrollBarData() { Minimum = this.Minimum, Maximum = this.Maximum, Showing = this.Showing, CurrentBegin = this.CurrentBegin, SuppressEvents = this.EventsSuppressed };
            }
            set
            {
                if (value != null)
                {
                    float oldValue = this._CurrentBegin;
                    this._Minimum = value.Minimum;
                    this._Maximum = value.Maximum;
                    this._Showing = value.Showing;
                    this._CurrentBegin = value.CurrentBegin;
                    this.RecalculateInnerArea();
                    float newValue = this._CurrentBegin;
                    if (!value.SuppressEvents)
                        this.OnValueChanged(oldValue, newValue, false);
                }
            }
        }
        /// <summary>
        /// Hodnota zobrazovaná v dolní krajní pozici (vizuálně vlevo / nahoře)
        /// </summary>
        public float Minimum { get { return _Minimum; } set { _Minimum = value; this.RecalculateInnerArea(); } }
        /// <summary>
        /// Hodnota zobrazovaná v horní krajní pozici (vizuálně vpravo / dole)
        /// </summary>
        public float Maximum { get { return _Maximum; } set { _Maximum = value; this.RecalculateInnerArea(); } }
        /// <summary>
        /// Viditelný úsek z celku, určuje velikost Thumbu. 
        /// Určuje, kolik z celkové velikosti (dokumentu) je nyní fyzicky zobrazeno. 
        /// Vztahuje se k rozdílu hodnot this.Maximum - this.Minimum.
        /// Pokud je nastaveno menší než 0, nebo větší než (Maximum - Minimum), je upraveno na odpovídající mezní hodnotu.
        /// </summary>
        public float Showing { get { return _Showing; } set { _Showing = value; this.RecalculateInnerArea(); } }
        /// <summary>
        /// Aktuální hodnota Scrollbaru, odpovídá pozici v zobrazovaném dokumentu, která je zobrazena na počátku viditelného prostoru
        /// </summary>
        public float CurrentBegin { get { return _CurrentBegin; } set { _CurrentBegin = value; this.RecalculateInnerArea(); } }
        /// <summary>
        /// Vnější souřadnice ScrollBaru. Z tvaru Scrollbaru se automaticky odvozuje jeho orientace (this.Orientation).
        /// </summary>
        public RectangleF Bounds { get { return _Bounds; } set { _Bounds = value; this.RecalculateInnerArea(); } }
        /// <summary>
        /// Orintace ScrollBaru, je odvozená ze souřadnic Bounds.
        /// </summary>
        public Orientation Orientation { get { return _Orientation; } }
        /// <summary>
        /// Vrstva, v níž se nachází tento prvek ve standardním stavu (mimo interaktivní editaci).
        /// Implicitní pro třídu EditorItem je hodnota Ground.
        /// </summary>
        public int LayerStandard { get; set; }
        /// <summary>
        /// Vrstva, v níž se nachází tento prvek v interaktivním stavu (drag: move, resize)
        /// Implicitní pro třídu EditorItem je hodnota Ground.
        /// </summary>
        public int LayerInteractive { get; set; }
        /// <summary>
        /// Příznak, který při hodnotě true potlačuje volání všech eventů při změnách.
        /// Nelze setovat, k řízení se používají metody SuspendEventChange() a ResumeEventChange().
        /// </summary>
        public bool EventsSuppressed { get; private set; }
        #endregion
		#region Public eventy
		/// <summary>
		/// Událost, kdy se ve ScrollBaru mění data
		/// </summary>
		public event EventHandler<ValueFloatChangedEventArgs> ValueChanged;
		/// <summary>
		/// Událost, kdy ScrollBar potřebuje překreslit sebe sama
		/// </summary>
		public event EventHandler CallDraw;
		/// <summary>
		/// Vyvolávač eventhandleru ValueChanged.
		/// Vrací hodnotu Cancel z argumentu.
		/// </summary>
		/// <param name="oldValue"></param>
		/// <param name="newValue"></param>
		/// <param name="callDraw"></param>
		/// <returns></returns>
		protected virtual bool OnValueChanged(float oldValue, float newValue, bool callDraw)
		{
			ValueFloatChangedEventArgs args = new ValueFloatChangedEventArgs(oldValue, newValue, callDraw);
			if (this.ValueChanged != null)
				this.ValueChanged(this, args);
			return args.Cancel;
		}
		/// <summary>
		/// Vyvolávač eventhandleru CallDraw
		/// </summary>
		protected virtual void OnCallDraw()
		{
			if (this.CallDraw != null)
				this.CallDraw(this, EventArgs.Empty);
		}
		#endregion
        #region Podpora interaktivity
        /// <summary>
        /// Určí, zda daná aktivita myši vede k nějaké změně prvku
        /// </summary>
		/// <param name="args">Data popisující stav controlu z hlediska interaktivity</param>
		/// <returns></returns>
		public void MouseChange(EditInteractiveEventArgs args)
        {
            if (!this.ScrollbarActive) return;

            Steward.Diagnostic.InfoAddLineWithTime("ScrollBar.DetectChangeOnMouseChange(args.MouseChange = %0)", args.MouseChange);
            switch (args.MouseChange)
            {
                case EditMouseChangeType.Enter:
					DetectChangeOnMouseEnter(args);
					break;
				case EditMouseChangeType.Move:
					DetectChangeOnMouseMove(args);
					break;
				case EditMouseChangeType.Leave:
					DetectChangeOnMouseLeave(args);
					break;
				case EditMouseChangeType.LeftDown:
					DetectChangeOnMouseLeftDown(args);
					break;
				case EditMouseChangeType.LeftDragBegin:
				case EditMouseChangeType.LeftDragMove:
					ReDrawLayerOnMouseLeftDrag(args);
					break;
				case EditMouseChangeType.LeftDragEnd:
				case EditMouseChangeType.LeftUp:
					ReDrawLayerOnMouseLeftUp(args);
					break;
                case EditMouseChangeType.WheelUp:
                    break;
                case EditMouseChangeType.WheelDown:
                    break;
            }
        }
		/// <summary>
		/// Zjistí, zda myš poté co vstoupila nad tento objekt způsobila změnu vizuálního stavu.
		/// Docela jistě ano (pokud není objekt Disable).
		/// Důležité je určit část, která je nyní pod myší = je v režimu Hot.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected void DetectChangeOnMouseEnter(EditInteractiveEventArgs args)
		{
			this.CurrentActivePart = this.FindPart(args.MousePointF);
			this.CurrentMouseState = EditMouseState.OnControl;
            args.ReDrawLayer(this.LayerStandard);
		}
		/// <summary>
		/// Zjisí, zda myš při pohybu nad tímto objektem způsobila změnu vizuálního stavu.
		/// To nastane po změně části, která se nachází pod myší.
		/// Důležité je určit část, která je nyní pod myší = je v režimu Hot.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected void DetectChangeOnMouseMove(EditInteractiveEventArgs args)
		{
			ScrollBarPart oldPart = this.CurrentActivePart;
			this.CurrentActivePart = this.FindPart(args.MousePointF);
            if (this.CurrentActivePart != oldPart)
                args.ReDrawLayer(this.LayerStandard);
		}
		/// <summary>
		/// Zjisí, zda myš při odchodu z tohoto objektu způsobila změnu vizuálního stavu.
		/// To nastane vždy.
		/// Důležité je vynulovat paměť aktivní části.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected void DetectChangeOnMouseLeave(EditInteractiveEventArgs args)
		{
			this.CurrentActivePart = ScrollBarPart.None;
			this.CurrentMouseState = EditMouseState.None;
            args.ReDrawLayer(this.LayerStandard);
		}
		/// <summary>
		/// Zjisí, zda myš při stisknutí levého tlačítka způsobila změnu vizuálního stavu.
		/// To nastane vždy.
		/// Důležité je provést / zahájit odpovádající akci, podle typ oblasti, na které se událost stala.
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected void DetectChangeOnMouseLeftDown(EditInteractiveEventArgs args)
		{
			this.CurrentActivePart = this.FindPart(args.MousePointF);
			this.CurrentMouseState = EditMouseState.MouseLeftDown;
			switch (this.CurrentActivePart)
			{
				case ScrollBarPart.BeginArrow:
					if (this.SetCurrentBeginOnSkip(-0.1f))
                        args.ReDrawLayer(this.LayerStandard);
					break;
				case ScrollBarPart.BeginTrack:
					if (this.SetCurrentBeginOnSkip(-0.95f))
                        args.ReDrawLayer(this.LayerStandard);
					break;
				case ScrollBarPart.Thumb:
					Rectangle area = this.GetPartArea(this.CurrentActivePart);
					this.MouseDragOffset = args.MousePointF.GetRelativePointFromBase(area.Location);
                    args.ReDrawLayer(this.LayerStandard);
                    args.ReDrawLayer(this.LayerInteractive);
					break;
				case ScrollBarPart.EndTrack:
					if (this.SetCurrentBeginOnSkip(0.95f))
                        args.ReDrawLayer(this.LayerStandard);
					break;
				case ScrollBarPart.EndArrow:
					if (this.SetCurrentBeginOnSkip(0.1f))
                        args.ReDrawLayer(this.LayerStandard);
					break;
			}
		}
		/// <summary>
		/// Zjistí, zda myš při tažení levého tlačítka způsobila změnu vizuálního stavu.
		/// </summary>
		/// <param name="args"></param>
		protected void ReDrawLayerOnMouseLeftDrag(EditInteractiveEventArgs args)
		{   // Zde nemá být detekce prostoru (this.CurrentAreaType), 
			//   protože tažení pomocí myši se týká již zmáčknutého prvku, 
			//   bez ohledu na to, zda se myš už nachází nad jiným prvkem.

			// Tahat lze pouze za Thumb, jiné prvky nejsou tahací:
			if (this.CurrentActivePart != ScrollBarPart.Thumb) return;

            if (this.DetectChangeOnDragThumb(args))
            {
                args.ReDrawLayer(this.LayerStandard);
                args.ReDrawLayer(this.LayerInteractive);
            }
		}
		/// <summary>
		/// Zjistí, zda myš při zvednutí levého tlačítka způsobila změnu vizuálního stavu.
		/// </summary>
		/// <param name="args"></param>
		protected void ReDrawLayerOnMouseLeftUp(EditInteractiveEventArgs args)
		{
			this.CurrentActivePart = this.FindPart(args.MousePointF);
			this.CurrentMouseState = (this.CurrentActivePart == ScrollBarPart.None ? EditMouseState.None : EditMouseState.OnControl);

            args.ReDrawLayer(this.LayerStandard);
            args.ReDrawLayer(this.LayerInteractive);
		}
		/// <summary>
		/// Provede přetažení prostoru Thumbu v závislosti na pohybu myši
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected bool DetectChangeOnDragThumb(EditInteractiveEventArgs args)
		{
            Steward.Diagnostic.InfoAddLineWithTime("ScrollBar.DetectChangeOnDragThumb()");
			switch (this.Orientation)
			{
				case System.Windows.Forms.Orientation.Horizontal:
					return this.DetectChangeOnDragThumbHorizontal(args);
				case System.Windows.Forms.Orientation.Vertical:
					return this.DetectChangeOnDragThumbVertical(args);
			}
			return false;
		}
		/// <summary>
		/// Provede přetažení prostoru Thumbu v závislosti na pohybu myši, na horizontálním ScrollBaru
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected bool DetectChangeOnDragThumbHorizontal(EditInteractiveEventArgs args)
		{
			Rectangle thumb = this.GetPartArea(ScrollBarPart.Thumb);
			Rectangle track = this.TrackArea;
			Point newLocation = args.MousePointF.GetRelativePointFromBase(this.MouseDragOffset);
			int x = newLocation.X;
			int minX = track.X;
			int maxX = track.X + track.Width - thumb.Width;
			if (x < minX) x = minX;
			if (x > maxX) x = maxX;
			if (x == thumb.X) return false;
			
			// Dochází ke skutečnému pohybu thumbu:
			float newValue = this.GetCurrentBeginFromPosition(x, thumb.Width, track.X, track.Width);
			bool changed = this.SetCurrentValue(newValue, true, true, false);
			if (!changed) return false;

			thumb.X = x;
			this.SetPartArea(ScrollBarPart.Thumb, thumb);
			return true;
		}
		/// <summary>
		/// Provede přetažení prostoru Thumbu v závislosti na pohybu myši, na horizontálním ScrollBaru
		/// </summary>
		/// <param name="args"></param>
		/// <returns></returns>
		protected bool DetectChangeOnDragThumbVertical(EditInteractiveEventArgs args)
		{
			Rectangle thumb = this.GetPartArea(ScrollBarPart.Thumb);
			Rectangle track = this.TrackArea;
			Point newLocation = args.MousePointF.GetRelativePointFromBase(this.MouseDragOffset);
			int y = newLocation.Y;
			int minY = track.Y;
			int maxY = track.Y + track.Height - thumb.Height;
			if (y < minY) y = minY;
			if (y > maxY) y = maxY;
			if (y == thumb.Y) return false;

			// Dochází ke skutečnému pohybu thumbu:
			float newValue = this.GetCurrentBeginFromPosition(y, thumb.Height, track.Y, track.Height);
			bool changed = this.SetCurrentValue(newValue, true, true, false);
			if (!changed) return false;

			thumb.Y = y;
			this.SetPartArea(ScrollBarPart.Thumb, thumb);
			return true;
		}
		/// <summary>
		/// Vypočte a vrátí hodnotu CurrentBegin na základě fyzické pozice Thumbu v prostoru Tracku.
		/// </summary>
		/// <param name="thumbPos">Pozice počátku Thumbu v souřadnicích na formuláři</param>
		/// <param name="thumbLength">Délka Thumbu (v přiměřeném směru)</param>
		/// <param name="trackBegin">Pozice počátku Tracku v souřadnicích na formuláři</param>
		/// <param name="trackLength">Délka Tracku (v přiměřeném směru)</param>
		/// <returns></returns>
		protected float GetCurrentBeginFromPosition(int thumbPos, int thumbLength, int trackBegin, int trackLength)
		{
			// Pozice počátku thumbu relativně k délce scrollbaru:
			decimal position = (decimal)(thumbPos - trackBegin) / (decimal)(trackLength);
			decimal logTop = position * (decimal)this.LogLength;
			return (float)logTop + this._Minimum;
		}
		/// <summary>
		/// Vypočte a zpracuje hodnotu CurrentBegin na základě skokové změny (trackbar, arrow).
		/// Pokud dojde ke změně, zpracuje ji (nastaví, obslouží eventy, vrátí true).
		/// Pokud ke změně nedojde, vrátí false.
		/// </summary>
		/// <param name="skip"></param>
		/// <returns></returns>
		protected bool SetCurrentBeginOnSkip(float skip)
		{
			float newValue = this._CurrentBegin + (skip * this._Showing);
			if (newValue < this._Minimum) newValue = this._Minimum;
			if (newValue > this._Maximum) newValue = this._Maximum;
			if (newValue == this._CurrentBegin) return false;

			bool changed = this.SetCurrentValue(newValue, true, true, false);
			if (!changed) return false;

			return true;
		}
		/// <summary>
		/// Zajistí vložení dané hodnoty do this._CurrentBegin, vyvolání následných přepočtů, vyvolání eventu a ReDraw (vše volitelně).
		/// Pokud eventhandler OnValueChanged nastaví Cancel = true, pak se změna neprovede.
		/// </summary>
		/// <param name="currentBegin"></param>
		/// <param name="recalc"></param>
		/// <param name="callEvent"></param>
		/// <param name="callDraw"></param>
		protected bool SetCurrentValue(float currentBegin, bool recalc, bool callEvent, bool callDraw)
		{
			bool cancel = false;
			float oldValue = this._CurrentBegin;
			this._CurrentBegin = currentBegin;
			if (callEvent)
				cancel = this.OnValueChanged(oldValue, currentBegin, false);
			if (cancel)
			{	// Storno:
				this._CurrentBegin = oldValue;
				return false;
			}

			if (recalc)
				this.RecalculateInnerArea();
			if (callDraw)
				this.OnCallDraw();
			
			return true;
		}
		/// <summary>
        /// Najde součástku, která se nachází na dané souřadnici.
        /// </summary>
        /// <param name="pointF">Souřadnice</param>
        /// <returns></returns>
        protected ScrollBarPart FindPart(PointF pointF)
        {
            Point point = Point.Round(pointF);
            if (this.BeginArrowArea.Contains(point)) return ScrollBarPart.BeginArrow;
			if (!this.ThumbArea.IsEmpty && this.ThumbArea.Contains(point)) return ScrollBarPart.Thumb;
            if (this.EndArrowArea.Contains(point)) return ScrollBarPart.EndArrow;
            if (this.BeginTrackArea.Contains(point)) return ScrollBarPart.BeginTrack;
            if (this.EndTrackArea.Contains(point)) return ScrollBarPart.EndTrack;
            return ScrollBarPart.None;
        }
		/// <summary>
		/// Vrátí souřadnice prostoru (Bounds) daného prvku
		/// </summary>
		/// <param name="part"></param>
		/// <returns></returns>
		protected Rectangle GetPartArea(ScrollBarPart part)
		{
			switch (part)
			{
				case ScrollBarPart.BeginArrow: return this.BeginArrowArea;
				case ScrollBarPart.BeginTrack: return this.BeginTrackArea;
				case ScrollBarPart.Thumb: return this.ThumbArea;
				case ScrollBarPart.EndTrack: return this.EndTrackArea;
				case ScrollBarPart.EndArrow: return this.EndArrowArea;
			}
			return Rectangle.Empty;
		}
		/// <summary>
		/// Nastaví dané souřadnice do daného prvku
		/// </summary>
		/// <param name="part"></param>
		/// <param name="area"></param>
		/// <returns></returns>
		protected void SetPartArea(ScrollBarPart part, Rectangle area)
		{
			switch (part)
			{
				case ScrollBarPart.BeginArrow: this.BeginArrowArea = area; break;
				case ScrollBarPart.BeginTrack: this.BeginTrackArea = area; break;
				case ScrollBarPart.Thumb: this.ThumbArea = area; break;
				case ScrollBarPart.EndTrack: this.EndTrackArea = area; break;
				case ScrollBarPart.EndArrow: this.EndArrowArea = area; break;
			}
		}
        /// <summary>
        /// Nyní aktivní část ScrollBaru (je nad ní myš, nebo je myš v ní stisknutá, viz CurrentMouseState)
        /// </summary>
        protected ScrollBarPart CurrentActivePart;
        /// <summary>
        /// Aktuální stav myši v rámci ScrollBaru (vztahuje se k části CurrentActivePart)
        /// </summary>
        protected EditMouseState CurrentMouseState;
		/// <summary>
		/// Offset myši proti počátku aktuální části
		/// </summary>
		protected Point MouseDragOffset;
        #endregion
        #region Přepočet dat na souřadnice, pomocné property
        /// <summary>
        /// přepočítá vnitřní souřadnice podle dat
        /// </summary>
        protected virtual void RecalculateInnerArea()
        {
            if (this.SuppessReCalc) return;

            // Zpracovat rozměry, orientaci, minimální tloušťku:
			RecalculateBoundsThick(10f);                                   // Zpracuje orientaci a MinThick
			Rectangle bounds = Rectangle.Truncate(this._Bounds);
			bool isHor = this.IsHorizontal;
			int thick = (isHor ? bounds.Height : bounds.Width);            // Tloušťka
			int length = (isHor ? bounds.Width : bounds.Height);           // Délka včetně krajních šipek (ty se nepočítají do aktivní plochy).

			// Délka vnitřní plochy v pixelech (trackpad = po níž se pohybuje thumb = jezdec) = bez dvou šířek (= Arrow):
			int trackLen = length - 2 * thick;

			// Délka thumbu v pixelech = jezdce, odpovídá poměru mezi velikostí viditelné plochy ku celkové velikosti dokumentu:
			//  Poznámka: ošetření vstupních logických dat je v getProperty typu this.LogXxx
			decimal sizeRatio = (decimal)trackLen / (decimal)this.LogLength;  // Poměr pixely / logická délka, vše vyjadřuje prostor pro dokument
            int thumbLen = (int)Math.Round((decimal)this.LogVisibleLength * sizeRatio, 0);
            if (thumbLen < 6)
                thumbLen = 6;

			// Pokud je thumb menší než (MinThumbSize) pixelů, nebudeme jej kreslit vůbec.
            // Pokud ScrollBar nebude aktivní (viditelné délka >= celková délka), taky se thumb nebude kreslit (scrollbar je neaktivní).
            bool scrollbarActive = (this.LogVisibleLength < this.LogLength);
            bool thumbVisible = scrollbarActive;
			if (!thumbVisible) thumbLen = 0;

			// Určíme souřadnice počátku a konce Thumbu = což odpovídá konci BeginTrack a začátku EndTrack:
            int thumbBeg = (int)Math.Round((decimal)this.LogVisibleBegin * sizeRatio, 0);
			int thumbEnd = thumbBeg + thumbLen;

            // Určení fyzických souřadnic všech prvků Scrollbaru:
			Rectangle beginArrowArea, beginTrackArea, thumbArea, trackArea, endTrackArea, endArrowArea;
            if (isHor)
            {   // Horizontální Scrollbar:
                int y = bounds.Y;
                int h = thick;
                int b = bounds.X + thick;
                int e = b + trackLen;
                beginArrowArea = new Rectangle(bounds.X, y, thick, h);
				beginTrackArea = new Rectangle(b, y, thumbBeg, h);
				thumbArea = (thumbVisible ? new Rectangle(b + thumbBeg, y, thumbLen, h) : Rectangle.Empty);
				trackArea = new Rectangle(b, y, trackLen, h);
				endTrackArea = new Rectangle(b + thumbEnd, y, trackLen - thumbEnd, h);
                endArrowArea = new Rectangle(e, y, thick, h);
            }
            else
            {   // Vertikální Scrollbar:
                int x = bounds.X;
                int w = thick;
                int b = bounds.Y + thick;
                int e = b + trackLen;
                beginArrowArea = new Rectangle(x, bounds.Y, w, thick);
				beginTrackArea = new Rectangle(x, b, w, thumbBeg);
				thumbArea = (thumbVisible ? new Rectangle(x, b + thumbBeg, w, thumbLen) : Rectangle.Empty);
				trackArea = new Rectangle(x, b, w, trackLen);
				endTrackArea = new Rectangle(x, b + thumbEnd, w, trackLen - thumbEnd);
                endArrowArea = new Rectangle(x, e, w, thick);
            }
            ScrollbarActive = scrollbarActive;
            BeginArrowArea = beginArrowArea;
            BeginTrackArea = beginTrackArea;
            ThumbArea = thumbArea;
			TrackArea = trackArea;
            EndTrackArea = endTrackArea;
            EndArrowArea = endArrowArea;
        }
        /// <summary>
        /// přepočítá vnitřní souřadnice podle dat, float
        /// </summary>
        protected virtual void RecalculateInnerAreaF()
        {
            if (this.SuppessReCalc) return;

            // Zpracovat rozměry, orientaci, minimální tloušťku:
            RecalculateBoundsThick(10f);                                   // Zpracuje orientaci a MinThick
            RectangleF bounds = this._Bounds;
            bool isHor = this.IsHorizontal;
            float thick = (isHor ? bounds.Height : bounds.Width);          // Tloušťka
            float length = (isHor ? bounds.Width : bounds.Height);         // Délka včetně krajních šipek (ty se nepočítají do aktivní plochy).

            // Délka vnitřní plochy v pixelech (trackpad = po níž se pohybuje thumb = jezdec) = bez dvou šířek (= Arrow):
            float trackLen = length - 2f * thick;

            // Délka thumbu v pixelech = jezdce, odpovídá poměru mezi velikostí viditelné plochy ku celkové velikosti dokumentu:
            //  Poznámka: ošetření vstupních logických dat je v getProperty typu this.LogXxx
            float sizeRatio = trackLen / this.LogLength;                   // Poměr pixely / logická délka, vše vyjadřuje prostor pro dokument
            float thumbLen = this.LogVisibleLength * sizeRatio;
            if (thumbLen < 6f)
                thumbLen = 6f;

            // Pokud je thumb menší než (MinThumbSize) pixelů, nebudeme jej kreslit vůbec.
            // Pokud ScrollBar nebude aktivní (viditelné délka >= celková délka), taky se thumb nebude kreslit (scrollbar je neaktivní).
            bool scrollbarActive = (this.LogVisibleLength < this.LogLength);
            bool thumbVisible = scrollbarActive;
            if (!thumbVisible) thumbLen = 0f;

            // Určíme souřadnice počátku a konce Thumbu = což odpovídá konci BeginTrack a začátku EndTrack:
            float thumbBeg = this.LogVisibleBegin * sizeRatio;
            float thumbEnd = thumbBeg + thumbLen;

            // Určení fyzických souřadnic všech prvků Scrollbaru:
            RectangleF beginArrowArea, beginTrackArea, thumbArea, trackArea, endTrackArea, endArrowArea;
            if (isHor)
            {   // Horizontální Scrollbar:
                float y = bounds.Y;
                float h = thick;
                float b = bounds.X + thick;
                float e = b + trackLen;
                beginArrowArea = new RectangleF(bounds.X, y, thick, h);
                beginTrackArea = new RectangleF(b, y, thumbBeg, h);
                thumbArea = (thumbVisible ? new RectangleF(b + thumbBeg, y, thumbLen, h) : RectangleF.Empty);
                trackArea = new RectangleF(b, y, trackLen, h);
                endTrackArea = new RectangleF(b + thumbEnd, y, trackLen - thumbEnd, h);
                endArrowArea = new RectangleF(e, y, thick, h);
            }
            else
            {   // Vertikální Scrollbar:
                float x = bounds.X;
                float w = thick;
                float b = bounds.Y + thick;
                float e = b + trackLen;
                beginArrowArea = new RectangleF(x, bounds.Y, w, thick);
                beginTrackArea = new RectangleF(x, b, w, thumbBeg);
                thumbArea = (thumbVisible ? new RectangleF(x, b + thumbBeg, w, thumbLen) : RectangleF.Empty);
                trackArea = new RectangleF(x, b, w, trackLen);
                endTrackArea = new RectangleF(x, b + thumbEnd, w, trackLen - thumbEnd);
                endArrowArea = new RectangleF(x, e, w, thick);
            }
            ScrollbarActive = scrollbarActive;
            BeginArrowArea = Rectangle.Round(beginArrowArea);
            BeginTrackArea = Rectangle.Round(beginTrackArea);
            ThumbArea = Rectangle.Round(thumbArea);
            TrackArea = Rectangle.Round(trackArea);
            EndTrackArea = Rectangle.Round(endTrackArea);
            EndArrowArea = Rectangle.Round(endArrowArea);
        }
        /// <summary>
		/// Určí orientaci scrollbaru (this.Orientation) podle jeho rozměrů.
		/// Určí šířku scrollbaru. Pokud je menší než je zadáno, nastaví ji na zadanou (mírně rozšíří ScrollBar dovnitř prostoru).
		/// </summary>
		/// <param name="minThick"></param>
		/// <returns></returns>
		private void RecalculateBoundsThick(float minThick)
		{
			RectangleF bounds = _Bounds;
			bool isHor = (bounds.Width > bounds.Height);
			this._Orientation = (isHor ? Orientation.Horizontal : Orientation.Vertical);
			float thick = (isHor ? bounds.Height : bounds.Width);
			if (thick < minThick)
			{   // Úprava Bounds:
				thick = minThick;
				if (isHor)
					bounds = new RectangleF(bounds.X, bounds.Bottom - thick, bounds.Width, thick);
				else
					bounds = new RectangleF(bounds.Right - thick, bounds.Y, thick, bounds.Height);
				_Bounds = bounds;
			}
		}
		/// <summary>
		/// Je pravítko vodorovné?
		/// </summary>
		protected bool IsHorizontal { get { return (this._Orientation == System.Windows.Forms.Orientation.Horizontal); } }
		/// <summary>
		/// Je pravítko svislé?
		/// </summary>
		protected bool IsVertical { get { return (this._Orientation == System.Windows.Forms.Orientation.Vertical); } }
		/// <summary>
		/// Logická velikost celkového prostoru (Maximum - Minimum), nejméně však 1f.
		/// Odpovídá celé ploše Scrollbaru.
		/// </summary>
		protected float LogLength
		{
			get 
			{
				float logLen = this._Maximum - this._Minimum;
				if (logLen < 1f) return 1f;
				return logLen;
			}
		}
		/// <summary>
		/// Logická velikost právě viditelné části, v rozsahu 0 až LogLength (včetně).
		/// Odpovídá velikosti Thumbu na Scrollbaru.
		/// </summary>
		protected float LogVisibleLength
		{
			get
			{
				float logVis = this._Showing;
				float logLen = this.LogLength;
				if (logVis < 0f) logVis = 0f;
				if (logVis > logLen) logVis = logLen;
				return logVis;
			}
		}
		/// <summary>
		/// Logická velikost části před viditelnou částí (0=viditelná část je přesně na počátku).
		/// Není menší než 0, a není větší než (celkový prostor - viditelný prostor).
		/// </summary>
		protected float LogVisibleBegin
		{
			get 
			{
				float beg = this._CurrentBegin - this._Minimum;
				if (beg < 0f) return 0f;
				float end = this.LogLength - this.LogVisibleLength;
				if (beg > end) return end;
				return beg;
			}
		}
		#endregion
		#region Vnitřní data
		/// <summary>
        /// Souřadnice tlačítka se šipkou doleva/nahoru
        /// </summary>
        protected Rectangle BeginArrowArea;
        /// <summary>
        /// Souřadnice trackbaru doleva/nahoru
        /// </summary>
        protected Rectangle BeginTrackArea;
        /// <summary>
        /// Souřadnice thumbu = tahací prvek scrollbaru.
		/// Pokud je Empty, pak se nemá kreslit (při délce menší než 6 px)
        /// </summary>
        protected Rectangle ThumbArea;
		/// <summary>
		/// Souřadnice obou trackbarů = v nich se může pohybovat thumb
		/// </summary>
		protected Rectangle TrackArea;
		/// <summary>
        /// Souřadnice trackbaru doprava/dolů
        /// </summary>
        protected Rectangle EndTrackArea;
        /// <summary>
        /// Souřadnice tlačítka se šipkou doprava/dolů
        /// </summary>
        protected Rectangle EndArrowArea;
        /// <summary>
        /// Scrollbar je aktivní (true) = v situaci, kdy je zobrazeno menší množství dat, než odpovídá celému dokumentu (Showing je menší než (Maximum - Minimum)).
        /// Neaktivní (false) = tehdy, když je zobrazeno vše, a Scrollbar nemá kam scrollovat.
        /// </summary>
        protected bool ScrollbarActive;
        /// <summary>
        /// Části ScrollBaru
        /// </summary>
        protected enum ScrollBarPart
        {
            /// <summary>Žádná</summary>
            None = 0,
            /// <summary>Button se šipkou ve směru Minimum</summary>
            BeginArrow,
            /// <summary>TrackPad ve směru Minimum</summary>
            BeginTrack,
            /// <summary>Thumb = tahací button uprostřed</summary>
            Thumb,
            /// <summary>TrackPad ve směru Maximum</summary>
            EndTrack,
            /// <summary>Button se šipkou ve směru Maximum</summary>
            EndArrow
        }
        #endregion
		#region Vykreslování
		/// <summary>
		/// Vykreslí svoje prvky v odpovídajícím stavu
		/// </summary>
        /// <param name="args">Data pro kreslení, obsahují grafiku</param>
        public void Paint(EditPaintEventArgs args)
		{
            Steward.Diagnostic.InfoAddLineWithTime("ScrollBar.Draw(Orientation=%0)", this.Orientation);

			bool scrollBarRendererIsSupported = ScrollBarRenderer.IsSupported;
			switch (this.Orientation)
			{
				case System.Windows.Forms.Orientation.Horizontal:
					if (scrollBarRendererIsSupported)
						this.DrawRendererHorizontal(args.Graphics);
					else
                        this.DrawDirectHorizontal(args.Graphics);
					break;
				case System.Windows.Forms.Orientation.Vertical:
					if (scrollBarRendererIsSupported)
                        this.DrawRendererVertical(args.Graphics);
					else
                        this.DrawDirectVertical(args.Graphics);
					break;
			}
		}
		/// <summary>
		/// Kreslí ScrollBar horizontální, s využitím Windows rendereru.
		/// </summary>
        /// <param name="graphics">Grafika pro kreslení</param>
        protected virtual void DrawRendererHorizontal(Graphics graphics)
		{
			ScrollBarRenderer.DrawArrowButton(graphics, BeginArrowArea, GetArrowState(ScrollBarArrowButtonState.LeftNormal, GetScrollBarState(ScrollBarPart.BeginArrow)));
			ScrollBarRenderer.DrawLeftHorizontalTrack(graphics, BeginTrackArea, GetScrollBarState(ScrollBarPart.BeginTrack));
			ScrollBarRenderer.DrawRightHorizontalTrack(graphics, EndTrackArea, GetScrollBarState(ScrollBarPart.EndTrack));
			ScrollBarRenderer.DrawArrowButton(graphics, EndArrowArea, GetArrowState(ScrollBarArrowButtonState.RightNormal, GetScrollBarState(ScrollBarPart.EndArrow)));

            if (this.ScrollbarActive)
            {
                Rectangle thumbArea = ThumbArea;
                if (!thumbArea.IsEmpty)
                {
                    ScrollBarState thumbState = GetScrollBarState(ScrollBarPart.Thumb);
                    ScrollBarRenderer.DrawHorizontalThumb(graphics, thumbArea, thumbState);

                    if ((thumbArea.Width - 4) > ScrollBarRenderer.GetThumbGripSize(graphics, thumbState).Width)
                        ScrollBarRenderer.DrawHorizontalThumbGrip(graphics, thumbArea, thumbState);
                }
            }
		}
		/// <summary>
		/// Kreslí ScrollBar horizontální, bez využití Windows rendereru, ručně.
		/// </summary>
        /// <param name="graphics">Grafika pro kreslení</param>
        protected virtual void DrawDirectHorizontal(Graphics graphics)
		{
		}
		/// <summary>
		/// Kreslí ScrollBar vertikální, s využitím Windows rendereru.
		/// </summary>
        /// <param name="graphics">Grafika pro kreslení</param>
        protected virtual void DrawRendererVertical(Graphics graphics)
		{
			ScrollBarRenderer.DrawArrowButton(graphics, BeginArrowArea, GetArrowState(ScrollBarArrowButtonState.UpNormal, GetScrollBarState(ScrollBarPart.BeginArrow)));
			ScrollBarRenderer.DrawUpperVerticalTrack(graphics, BeginTrackArea, GetScrollBarState(ScrollBarPart.BeginTrack));
			ScrollBarRenderer.DrawLowerVerticalTrack(graphics, EndTrackArea, GetScrollBarState(ScrollBarPart.EndTrack));
			ScrollBarRenderer.DrawArrowButton(graphics, EndArrowArea, GetArrowState(ScrollBarArrowButtonState.DownNormal, GetScrollBarState(ScrollBarPart.EndArrow)));

            if (this.ScrollbarActive)
            {
                Rectangle thumbArea = ThumbArea;
                if (!thumbArea.IsEmpty)
                {
                    ScrollBarState thumbState = GetScrollBarState(ScrollBarPart.Thumb);
                    ScrollBarRenderer.DrawVerticalThumb(graphics, thumbArea, thumbState);

                    if ((thumbArea.Height - 4) > ScrollBarRenderer.GetThumbGripSize(graphics, thumbState).Height)
                        ScrollBarRenderer.DrawVerticalThumbGrip(graphics, thumbArea, thumbState);
                }
            }
		}
		/// <summary>
		/// Kreslí ScrollBar vertikální, bez využití Windows rendereru, ručně.
		/// </summary>
        /// <param name="graphics">Grafika pro kreslení</param>
        protected virtual void DrawDirectVertical(Graphics graphics)
		{

		}
		/// <summary>
		/// Vrátí stav daného prvku ScrollBaru z hlediska jeho vykreslování
		/// </summary>
		/// <param name="part"></param>
		/// <returns></returns>
		protected ScrollBarState GetScrollBarState(ScrollBarPart part)
		{
			if (!this.ScrollbarActive) return ScrollBarState.Disabled;
			if (this.CurrentActivePart == ScrollBarPart.None || part != this.CurrentActivePart) return ScrollBarState.Normal;
			switch (this.CurrentMouseState)
			{
				case EditMouseState.None:
				case EditMouseState.Outside:
					return ScrollBarState.Normal;
				case EditMouseState.OnControl:
				case EditMouseState.MouseRightDown:
					return ScrollBarState.Hot;
				case EditMouseState.MouseLeftDown:
					return ScrollBarState.Pressed;
			}
			return ScrollBarState.Normal;
		}
		/// <summary>
		/// Vrátí druh Arrow pro zadání buttonu ve stavu Normal (například ScrollBarArrowButtonState.LeftNormal = 9), 
		/// pro daný aktuální stav prvku (například ScrollBarState.Pressed = 3),
		/// vrátí hodnotu ScrollBarArrowButtonState.LeftPressed = 11 (= (9 + 3 - 1)).
		/// </summary>
		/// <param name="normalState"></param>
		/// <param name="currentState"></param>
		/// <returns></returns>
		protected ScrollBarArrowButtonState GetArrowState(ScrollBarArrowButtonState normalState, ScrollBarState currentState)
		{
			int add = (int)currentState;           // Stavy jsou numerické, v pořadí: Normal, Hot, Pressed, Disable, počínaje 1
			int arrow = (int)normalState;          // Stavy Arrow (ScrollBarArrowButtonState) jsou v pořadí: podobně jako (ScrollBarState)
			return (ScrollBarArrowButtonState)(arrow + add - 1);
		}
		#endregion
		#region Potlačení přepočtů při výchozí hromadné inicializaci hodnot (IDIsposable wrapper)
        /// <summary>
        /// <para>
        /// Metoda vrací objekt, který po dobu své existence potlačuje vnitřní rekalkulace scrollbaru.
        /// Typicky se používá po dobu prvotní inicializace objektu, aby se po změně každé jednotlivé property nespouštěly vnitřní přepočty.
        /// Na konci života objektu se rekalkulace provede povinně.
        /// </para>
        /// <para>
        /// Použití: using(ScrollBar.SuppresRecalculate()) { nastavení property ... }
        /// </para>
        /// </summary>
        /// <returns></returns>
        public IDisposable SuppressRecalculate()
        {
            this.SuppessReCalc = true;
            return new DisposeActionCaller<bool>(this._ResumeRecalculate, true);  // Instance třídy DisposeActionCaller zajistí, že při jejím Dispose se zavolá daná metoda s danými daty.
        }
        /// <summary>
        /// Privátní metoda volaná po ukončení režimu SuppressRecalculate (volá ji instance třídy DisposeActionCaller z jejího Dispose)
        /// </summary>
        /// <param name="executeEvents"></param>
        private void _ResumeRecalculate(bool executeEvents)
        {
            this.SuppessReCalc = false;
            this.RecalculateInnerArea();
        }
		/// <summary>
		/// Proměnná, která řídí potlačení přepočtů. Proměnnou řídí wrapper EditWrap.
		/// true = přepočty jsou potlačeny.
		/// </summary>
        protected bool SuppessReCalc;
        #endregion
    }
    #region class ScrollBarData : Třída pro hromadné předání dat do/z Scrollbaru.
    /// <summary>
    /// ScrollBarData : Třída pro hromadné předání dat do/z Scrollbaru.
    /// Umožňuje nastavit hodnoty scrollbaru jedním setováním, včetně možností potlačení eventů.
    /// </summary>
    public class ScrollBarData
    {
        /// <summary>
        /// Vizualizace
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Min=" + this.Minimum.ToString() + "; Max=" + this.Maximum.ToString() + "; Vis=" + this.Showing.ToString() + "; Begin=" + this.CurrentBegin.ToString();
        }
        /// <summary>
        /// Hodnota zobrazovaná v dolní krajní pozici (vizuálně vlevo / nahoře)
        /// </summary>
        public float Minimum { get; set; }
        /// <summary>
        /// Hodnota zobrazovaná v horní krajní pozici (vizuálně vpravo / dole)
        /// </summary>
        public float Maximum { get; set; }
        /// <summary>
        /// Viditelný úsek z celku, určuje velikost Thumbu. 
        /// Určuje, kolik z celkové velikosti (dokumentu) je nyní fyzicky zobrazeno. 
        /// Vztahuje se k rozdílu hodnot this.Maximum - this.Minimum.
        /// Pokud je nastaveno menší než 0, nebo větší než (Maximum - Minimum), je upraveno na odpovídající mezní hodnotu.
        /// </summary>
        public float Showing { get; set; }
        /// <summary>
        /// Aktuální hodnota Scrollbaru, odpovídá pozici v zobrazovaném dokumentu, která je zobrazena na počátku viditelného prostoru
        /// </summary>
        public float CurrentBegin { get; set; }
        /// <summary>
        /// Potlačení eventů při vkládání dat do Scrollbaru.
        /// Pokud bude true, pak se data do Scrollbaru vloží, Scrollbar si interně přepočítá své hodnoty, ale nevyvolá událost Change.
        /// </summary>
        public bool SuppressEvents { get; set; }
    }
    #endregion
}
